/*	--- 修改请求 --- 


【Filter】可以对【请求】进行【预处理】，因此，我们可以把【很多公共预处理逻辑】放到【Filter】中完成。

考察这样【一种需求】：我们在【Web应用】中,经常需要【处理用户上传的文件】
  
例如: 【一个UploadServlet】可以简单地编写如下： */
@WebServlet(urlPatterns = "/upload/file")
public class UploadServlet extends HttpServlet {
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {

		// 读取Request Body
		InputStream input = req.getInputStream(); // InputStream
		ByteArrayOutputStream output = new ByteArrayOutputStream(); // OutputStream

		byte[] buffer = new byte[1024]; // Cache

		for (;;) {
			int len = input.read(buffer);

			if (len == -1) {
				break;
			}

			output.write(buffer, 0, len); // cache内容,0,len 写入OutputStream
		}

		// TODO: 写入文件

		// 显示上传结果
		String uploadedText = output.toString(StandardCharsets.UTF_8);

		PrintWriter pw = resp.getWriter();
		pw.write("<h1>Uploaded:</h1>");
		pw.write("<pre><code>");
		pw.write(uploadedText);
		pw.write("</code></pre>");

		pw.flush();
	}
}

/*
Q: 但是要保证【文件上传的完整性】怎么办？
A: 在【哈希算法】一节中，我们知道，如果在【上传文件】的同时，把【文件的哈希】也传过来，【服务器端】做一个【验证】，就可以确保【用户上传的文件】一定是【完整的】。
(即:传输 内容 + SHA256 )

这个【验证逻辑】非常适合写在【ValidateUploadFilter】中，因为它可以【复用】。

我们先写一个【简单的版本】，快速实现【ValidateUploadFilter的逻辑】： */
@WebFilter("/upload/*")
public class ValidateUploadFilter implements Filter {

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,ServletException {

		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;

		// 获取【客户端】传入的【签名方法+签名】
		String digest = req.getHeader("Signature-Method"); // 签名方法
		String signature = req.getHeader("Signature"); // 签名

		// 若"Signature-Method","Signature"空
		if (digest == null || digest.isEmpty() || signature == null || signature.isEmpty()) {
			sendErrorPage(resp, "Missing signature.");
			return ;
		}

		// 读取【Request的Body】, 并验证【签名】
		MessageDigest md = getMessageDigest(digest); // 根据【签名方法】获取【摘要】
		InputStream input = new DigestInputStream(request.getInputStream(), md); 
			// new DigestInputStream(输入原始数据, 摘要算法), return 数据摘要(2进制)

		byte[] buffer = new byte[1024];

		for (;;) {
			int len = input.read(buffer);

			if (len == -1) {
				break;
			}
		}

		String actual = toHexString(md.digest()); // 数据digest 2进制->16进制
			// actual  adj.  真正的

		if (!signature.equals(actual)) {
			sendErrorPage(resp, "Invalid signature.");
			return ;
		}

		// 验证成功后,继续处理
		chain.doFilter(request, response);
	}

	// 将byte[]转换为hex string
	private String toHexString(byte[] digest) {
		StringBuilder sb = new StringBuilder();

		for (byte b : digest) {
			sb.append(String.format("%02x", b)); 
				// %02x: 以16进制的格式输出整数类型的数值,输出域宽为2,右对齐,不足的用字符0替代。
		}

		return sb.toString();
	}

	// 根据【名称】创建【MessageDigest】
	private MessageDigest getMessageDigest(String name) throws ServletException {
	// 传入 摘要算法名
		try {
			return MessageDigest.getInstance(name); // 根据【摘要算法名】获取【消息摘要实例】
		} catch (NoSuchAlgorithmException e) {
			throw new ServletException(e);
		}
	}

	// 发送一个【错误响应】
	private void sendErrorPage(HttpServletResponse resp, String errorMessage) throws IOException {
		resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);

		PrintWriter pw = resp.getWriter();
		pw.write("<html><body><h1>");
		pw.write(errorMessage);
		pw.write("</h1></body></html>");

		pw.flush();
	}
}


/*
这个【ValidateUploadFilter的逻辑】似乎没有问题，我们可以用【curl命令】测试：

---------------------------------------------------------------
$ curl http://localhost:8080/upload/file -v -d 'test-data' \
  -H 'Signature-Method: SHA-1' \
  -H 'Signature: 7115e9890f5b5cc6914bdfa3b7c011db1cdafedb' \
  -H 'Content-Type: application/octet-stream'
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /upload/file HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: * /*
> Signature-Method: SHA-1
> Signature: 7115e9890f5b5cc6914bdfa3b7c011db1cdafedb
> Content-Type: application/octet-stream
> Content-Length: 9
> 
* upload completely sent off: 9 out of 9 bytes
< HTTP/1.1 200 
< Transfer-Encoding: chunked
< Date: Thu, 30 Jan 2020 13:56:39 GMT
< 
* Connection #0 to host localhost left intact
<h1>Uploaded:</h1><pre><code></code></pre>
* Closing connection 0
----------------------------------------------------

【ValidateUploadFilter】对【签名】进行验证的【逻辑】是没有问题的

但是，细心的童鞋注意到，【UploadServlet】并未读取到【任何数据】！


===================================================

这里的原因是: 对【HttpServletRequest】进行读取时，【只能读取一次】。


Q: 如果【Filter】调用getInputStream()读取了【一次数据】，【后续Servlet】处理时，【再次读取】，将【无法读到任何数据】。怎么办？
(HttpServletRequest同样的数据只能读取一次)

A: 这个时候，我们需要一个【“伪造”的HttpServletRequest】，【具体做法】是使用【代理模式】，对【getInputStream() 和 getReader() 】返回【一个新的流】： */
class ReReadableHttpServletRequest extends HttpServletRequestWrapper {

	private byte[] body;
	private boolean open = false;

	public ReReadableHttpServletRequest(HttpServletRequest request, byte[] body) {
		super(request);
		this.body = body;
	}

	// 用于new InputStreamReader(InputStream, Encoding)调用, return InputStream
	public ServetInputStream getInputStream() throws IOException {
		if (open) {
			throw new IllegalStateException("Cannot re-open input stream!");
		}

		open = true;

		return new ServletInputStream() {
			private int offset = 0; // offset  n.  补偿,开端,出发

			public boolean isFinished() {
				return offset >= body.length;
			}

			public boolean isReady() {
				return true;
			}

			public void setReadListener(ReadListener listener) {
				// TODO
			}

			public int read() throws IOException {
				if (offset >= body.length) {
					return -1;
				}

				int n = body[offset]; // 读取body[offset]
				offset++; // 每次读取一个byte
					// byte[] body;

				return n;
			}
		};
	}

	// 返回Reader
	public BufferedReader getReader() throws IOException {
		if (open) {
			throw new IllegalStateException("Cannot re-open reader!");
		}

		open = true;

		retun new BufferedReader(new InputStreamReader(getInputStream(), "UTF-8"));
	}
}


/*
注意观察【class ReReadableHttpServletRequest的构造方法】, 它保存了【class ValidateUploadFilter】读取的【byte[]内容】
PS: 通过 super(request)

【class ReReadableHttpServletRequest】同时在【调用getInputStream()时】, 通过【byte[]】构造了【一个新的ServletInputStream】。

-----------------------------------

编写了【class ReReadableHttpServletRequest】, 还需要稍微修改一下【clss ValidateUploadFilter implements Filter】中的【chain.doFilter(req, resp)方法】

然后，我们在【class ValidateUploadFilter】中，把【doFilter()调用时】传给下一个处理者的【HttpServletRequest】替换为【我们自己“伪造”的ReReadableHttpServletRequest】： */
@WebFilter("/user/*")
public class ValidateUploadFilter implements Filter {

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,ServletException {
	//...
	chain.doFilter(new ReReadableHttpServletRequest(req, output.toByteArray()), response);
	}
}



/*
再注意到, 我们编写的【class ReReadableHttpServletRequest】时，是从【class HttpServletRequestWrapper继承】，而不是直接实现【HttpServletRequest接口】。

这是因为，【Servlet的每个新版本】都会对【接口】增加一些【新方法】，【从HttpServletRequestWrapper继承】可以确保【新方法】被【正确地覆写】了，

因为【class HttpServletRequestWrapper】是由【Servlet的jar包】提供的，目的就是为了让我们方便地实现对【HttpServletRequest接口的代理】。

-------------------------------------

我们总结一下对【HttpServletRequest接口】进行【代理】的步骤：

1. 从【HttpServletRequestWrapper】继承【一个XxxHttpServletRequest】，需要传入原始的HttpServletRequest实例；
	
2. 覆写【某些方法】，使得【新的XxxHttpServletRequest实例】看上去“改变”了【原始的HttpServletRequest实例】；
	
3. 在doFilter()中传入新的XxxHttpServletRequest实例。


----------------------------------


虽然整个Filter的代码比较复杂，但它的好处在于：

· 【这个Filter】在【整个处理链中】实现了【灵活的“可插拔”特性】
· 即【是否启用】对【Web应用程序的其他组件】（Filter、Servlet）完全没有影响。

====================================================================

#	练习

使用Filter修改请求 */


/*================================================================


#	----- 修改请求 の 小结 ----- 


1. 借助HttpServletRequestWrapper，我们可以在Filter中实现对原始HttpServletRequest的修改。


------------------------------


代码模板: */

// Filter
@WebFilter("/upload/*")
public class ValidateUploadFilter implements Filter {

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,ServletException {

		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;

		// 获取【客户端】传入的【属性: 签名方法+签名】
		String digest = req.getHeader("Signature-Method"); // 签名方法
		String signature = req.getHeader("Signature"); // 签名

		// 若"Signature-Method","Signature"空
		if (digest == null || digest.isEmpty() || signature == null || signature.isEmpty()) {
			sendErrorPage(resp, "Missing signature.");
			return ;
		}

	// 读取【Request的Body】并【验证签名】:
		// 获取【Digest算法实例】; 
		MessageDigest md = getMessageDigest(digest); // 根据【签名方法】获取【摘要】

		// Digest输入数据(原始数据, 算法实例), return 摘要后的InputStream
		InputStream input = new DigestInputStream(request.getInputStream(), md); 
			// new DigestInputStream(输入原始数据, 摘要算法), return 数据摘要(2进制)

		byte[] buffer = new byte[1024];

		// 将 摘要后的InputStream 存入 buffer, return len
		for (;;) {
			int len = input.read(buffer); 

			if (len == -1) {
				break;
			}
		}

		String actual = toHexString(md.digest()); 
			// MessageDigest.digest(), 2进制->16进制
			// actual  adj.  真正的

		// 验证【十六进制 签名】
		if (!signature.equals(actual)) { 
			sendErrorPage(resp, "Invalid signature.");
			return ;
		}


	// 验证成功后,继续处理
		chain.doFilter(new ReReadableHttpServletRequest(req, output.toByteArray()), response);
	}

	// 将byte[]转换为hex string
	private String toHexString(byte[] digest) {
		StringBuilder sb = new StringBuilder();

		for (byte b : digest) {
			sb.append(String.format("%02x", b)); 
				// %02x: 以16进制的格式输出整数类型的数值,输出域宽为2,右对齐,不足的用字符0替代。
		}

		return sb.toString();
	}

	// 根据【名称】创建【MessageDigest】
	private MessageDigest getMessageDigest(String name) throws ServletException {
		try {
			return MessageDigest.getInstance(name);
		} catch (NoSuchAlgorithmException e) {
			throw new ServletException(e);
		}
	}

	// 发送一个【错误响应】
	private void sendErrorPage(HttpServletResponse resp, String errorMessage) throws IOException {
		resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);

		PrintWriter pw = resp.getWriter();
		pw.write("<html><body><h1>");
		pw.write(errorMessage);
		pw.write("</h1></body></html>");

		pw.flush();
	}
}

// HttpServletRequestWrapper 【中继request 修改请求】
class ReReadableHttpServletRequest extends HttpServletRequestWrapper {

	private byte[] body; // output.toByteArray()
	private boolean open = false;

	public ReReadableHttpServletRequest(HttpServletRequest request, byte[] body) {
		super(request);
		this.body = body;
	}

	// 用于new InputStreamReader(InputStream, Encoding)调用, return InputStream
	public ServetInputStream getInputStream() throws IOException {
		if (open) {
			throw new IllegalStateException("Cannot re-open input stream!");
		}

		open = true;

		return new ServletInputStream() {
			private int offset = 0; // offset  n.  补偿,开端,出发

			public boolean isFinished() {
				return offset >= body.length;
			}

			public boolean isReady() {
				return true;
			}

			public void setReadListener(ReadListener listener) {
				// TODO
			}

			public int read() throws IOException {
				if (offset >= body.length) {
					return -1;
				}

				int n = body[offset]; // 读取body[offset]
				offset++; // 每次读取一个byte
					// byte[] body;

				return n;
			}
		};
	}

	// return Reader
	public BufferedReader getReader() throws IOException {
		if (open) {
			throw new IllegalStateException("Cannot re-open reader!");
		}

		open = true;

		retun new BufferedReader(new InputStreamReader(getInputStream(), "UTF-8"));
			// 调用 this.getInputStream()
	}
}



















